<template>
	<view :class="maxCount>1?'uploadFile uploadFile_max':'uploadFile'">
		<uv-upload ref="uvUpload" :uploadText="uploadText" :id="'my-drag-files'+name" :name="String(name)"
			:accept="accept" :capture="capture" class="uv_upload" :fileList="fileList" :multiple="maxCount>1?true:false"
			:maxCount="maxCount" @afterRead="afterRead" @delete="deletePic" :previewFullImage="true" :width="viewWidth"
			:height="viewHeight" :imageMode="mode">
		</uv-upload>
	</view>
</template>

<script>
	import config from '@/common/config'
	import CryptoJS from 'crypto-js';
	import {
		postName
	} from '@/common/api.js';
	import Md5File from '@/util/md5File.js';
	
	export default {
		components: {},
		props: {
			fileList: {
				type: Array,
				default: []
			},
			accept: {
				type: String,
				default: 'all'
			},
			parentId: {
				type: String,
				default: null
			},
			mode: {
				type: String,
				default: 'widthFix'
			},
			uploadText: {
				type: String,
				default: ''
			},
			maxSize: {
				type: [Number, String],
				default: 3 * 1024 * 1024
			},
			limit: {
				type: [Number, String],
				default: 1 * 1024 * 1024 * 1024
			},
			minWidth: {
				type: [Number, String],
				default: 1
			},
			maxCount: {
				type: [Number, String],
				default: '1'
			},
			name: {
				type: [Number, String],
				default: '1'
			},
			height: {
				type: [Number, String],
				default: 0
			},
			width: {
				type: [Number, String],
				default: 0
			},
		},
		data() {
			return {
				viewWidth: this.height,
				viewHeight: this.width,
				obj: {},
				capture: ['album', 'camera'],
				type: '',
				resdata: [],
				afterIndex: 0,
				
				// OSS相关配置
				ossConfig: null,
				CHUNK_SIZE: 5 * 1024 * 1024, // 5MB 分片大小
				MAX_RETRIES: 3, // 最大重试次数
				CONCURRENT_UPLOADS: 3, // 并发上传数量
			}
		},
		computed: {
			allowType() {
				let image = ["png", "jpeg", "jpg", "gif", "webp", "svg", 'bmp'];
				let video = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
				if (this.accept == 'image') {
					return image;
				}
				if (this.accept == 'video') {
					return video;
				}

				if (this.accept == 'media') {
					return ['mp3', 'wav', ...image, ...video];
				}

				if (this.accept == 'file') {
					return ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
				}

				if (this.accept.includes(',')) {
					return this.accept.split(',');
				} else {
					return;
				}
			},
		},
		mounted() {
			if (!this.height && !this.width) {
				this.getViewSize(true);
			}

			//添加监听
			let elem = document.getElementById('my-drag-files' + this.name);
			elem.addEventListener('drop', this.ondrop, false);
			elem.addEventListener('dragleave', this.ondropprevent);
			elem.addEventListener('dragenter', this.ondropprevent);
			elem.addEventListener('dragover', this.ondropprevent);
			this.elem = elem;
		},
		// 销毁时
		beforeDestroy() {
			const {
				elem
			} = this;
			//移除监听
			elem.removeEventListener('drop', this.ondrop);
			elem.removeEventListener('dragleave', this.ondropprevent);
			elem.removeEventListener('dragenter', this.ondropprevent);
			elem.removeEventListener('dragover', this.ondropprevent);
		},
		beforeUnmount() {
			const {
				elem
			} = this;
			//移除监听
			elem.removeEventListener('drop', this.ondrop);
			elem.removeEventListener('dragleave', this.ondropprevent);
			elem.removeEventListener('dragenter', this.ondropprevent);
			elem.removeEventListener('dragover', this.ondropprevent);
		},
		methods: {
			handleClick() {
				this.$refs.uvUpload.chooseFile();
			},
			ondrop(event) {
				let files = event.dataTransfer.files;
				let list = [];
				for (let i = 0; i < files.length; i++) {
					let url = this.getTempFilePath(files[i]);
					list.push({
						name: files[i].name,
						size: files[i].size,
						type: files[i].type,
						url: url,
					});
				}
				this.afterRead({
					file: list,
					name: String(this.name),
					index: this.fileList.length
				});
				this.ondropprevent(event);
				return;
			},
			ondropprevent(event) {
				event.stopPropagation();
				event.preventDefault();
			},
			toast(e) {
				this.$emit('toast', e);
			},
			getTempFilePath(file) {
				let path;
				if (window.createObjectURL) path = window.createObjectURL(file);
				else if (window.webkitURL) path = window.webkitURL.createObjectURL(file);
				else if (window.URL) path = window.URL.createObjectURL(file);
				else console.error('getTempFilePath has error');
				return path;
			},
			getViewSize(e) {
				var that = this;
				const query = uni.createSelectorQuery().in(this);
				query.select('.uv_upload').boundingClientRect(data => {
					if (data) {
						this.obj = data;
						if (e) {
							that.viewWidth = data.width;
							that.viewHeight = parseInt(that.viewWidth / 1.9);
							this.obj.height = that.viewHeight;
						}
					}
				}).exec();
			},
			// 删除图片
			deletePic(e) {
				this.resdata.splice(e.index, 1);
				this.fileList.splice(e.index, 1);
			},
			deletes(url) {
				for (let y = 0; y < this.fileList.length; y++) {
					if (this.fileList[y].url == url) {
						this.fileList.splice(y, 1);
						return;
					}
				}
			},
			// 获取OSS配置
			async getOSSConfig() {
				if (this.ossConfig && Date.now() < this.ossConfig.expiration) {
					return this.ossConfig;
				}
				
				try {
					const res = await postName('/oss/getOSSToken', {}, {
						custom: {
							auth: true,
							toast: true,
							catch: true
						},
					});
					
					if (res.code === 200) {
						this.ossConfig = {
							...res.data,
							expiration: Date.now() + (55 * 60 * 1000) // 55分钟后过期
						};
						return this.ossConfig;
					} else {
						throw new Error(res.msg || '获取OSS配置失败');
					}
				} catch (error) {
					console.error('获取OSS配置失败:', error);
					this.toast({
						message: '获取上传配置失败'
					});
					throw error;
				}
			},
			
			// 生成OSS上传文件名
			generateOSSFileName(fileInfo, fileHash) {
				const ext = fileInfo.name.split('.').pop().toLowerCase();
				const timestamp = Date.now();
				const randomStr = Math.random().toString(36).substring(2, 8);
				return `${fileHash}_${timestamp}_${randomStr}.${ext}`;
			},
			
			// 新增图片
			async afterRead(event) {
				console.time('total-upload-time');
				var that = this;
				this.$emit('focus');
				
				// 当设置 multiple 为 true 时, file 为数组格式，否则为对象格式
				let lists = [].concat(event.file)
				let fileListLen = this.fileList.length
				lists.map((item) => {
					this.fileList.push({
						...item,
						status: 'uploading',
						message: '上传中'
					})
				})
				
				for (let i = 0; i < lists.length; i++) {
					if (this.maxCount < this.fileList.length) {
						this.deletes(lists[i].url);
						continue;
					}
					
					// 获取文件扩展名并验证
					const typeExt = lists[i].type.split('/')[0] || lists[i].type;
					const ext = lists[i].name.split('.').pop().toLowerCase();
					if (this.accept != "all" && this.allowType && this.allowType[0]) {
						if (this.accept != typeExt || !this.allowType.some(ele => ele.includes(ext))) {
							this.toast({
								message: `请上传 ${this.allowType.join("、").replaceAll('.', '')} 格式文件`
							})
							this.deletes(lists[i].url);
							continue;
						}
					}

					if (this.accept == 'image' && Number(lists[i].size) > Number(this.maxSize)) {
						this.toast({
							message: '图片大小超过 ' + this.maxSize / 1024 / 1024 + ' M'
						})
						this.deletes(lists[i].url);
						continue;
					} else {
						if (Number(lists[i].size) > Number(this.limit)) {
							this.toast({
								message: '文件大小超过 ' + this.limit / 1024 / 1024 + ' M'
							})
							this.deletes(lists[i].url);
							continue;
						}
					}
					
					let image = lists[i].url;

					// 并行执行MD5计算和图片信息获取
					const [images, fileHash] = await Promise.all([
						this.imageInfoPromise(lists[i].url),
						Md5File.calculateFileHash(lists[i], this.CHUNK_SIZE),
					])

					if (images) {
						this.type = 'image';
						if (Number(images.width) < Number(this.minWidth)) {
							this.toast({
								message: '图片尺寸不小于' + this.minWidth + 'px'
							})
							this.deletes(lists[i].url);
							continue;
						}
					} else {
						this.type = 'file';
					}

					// 直接上传到OSS
					const data = await this.uploadFileToOSS(lists[i], fileHash);
					if (data && data.url) {
						let item = this.fileList[fileListLen]
						this.fileList.splice(fileListLen, 1, Object.assign(item, {
							status: 'success',
							message: '',
							url: data.url,
							path: data.path,
							base64: data.base64 || '',
							baseimg: data.baseimg || ''
						}))

						let ites = this.resdata[fileListLen] || {};
						this.resdata.splice(fileListLen, 1, Object.assign(ites, {
							url: data.url,
							path: data.path,
							base64: data.base64 || '',
							baseimg: data.baseimg || ''
						}))
						fileListLen++
					}
				}
				console.timeEnd('total-upload-time');
			},
			
			// 直接上传文件到OSS
			async uploadFileToOSS(fileInfo, fileHash) {
				try {
					// 获取OSS配置
					const ossConfig = await this.getOSSConfig();
					
					// 生成OSS文件名
					const ossFileName = this.generateOSSFileName(fileInfo, fileHash);
					const ossKey = `uploads/${ossFileName}`;
					
					// 根据文件大小选择上传方式
					if (fileInfo.size <= this.CHUNK_SIZE) {
						// 小文件直接上传
						return await this.uploadSmallFile(fileInfo, ossConfig, ossKey);
					} else {
						// 大文件分片上传
						return await this.uploadLargeFile(fileInfo, ossConfig, ossKey, fileHash);
					}
					
				} catch (error) {
					console.error('上传文件到OSS失败:', error);
					this.toast({
						message: '文件上传失败'
					});
					return null;
				}
			},
			
			// 上传小文件（直接PUT）
			async uploadSmallFile(fileInfo, ossConfig, ossKey) {
				return new Promise((resolve, reject) => {
					// 读取文件内容
					const xhr = new XMLHttpRequest();
					
					xhr.upload.onprogress = (event) => {
						if (event.lengthComputable) {
							const progress = Math.round((event.loaded / event.total) * 100);
							this.updateUploadProgress(progress);
						}
					};
					
					xhr.onload = () => {
						if (xhr.status === 200) {
							const fileUrl = `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}`;
							
							// 保存文件信息到数据库
							this.saveFileRecord({
								name: fileInfo.name,
								path: ossKey,
								url: fileUrl,
								size: fileInfo.size,
								type: fileInfo.type,
								fileHash: fileHash
							}).then(fileRecord => {
								resolve({
									url: fileUrl,
									path: ossKey,
									...fileRecord
								});
							}).catch(reject);
						} else {
							reject(new Error(`上传失败: ${xhr.status}`));
						}
					};
					
					xhr.onerror = () => reject(new Error('上传失败'));
					
					// 读取文件并上传
					this.readFileAsArrayBuffer(fileInfo).then(arrayBuffer => {
						xhr.open('PUT', `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}`, true);
						xhr.setRequestHeader('Authorization', ossConfig.authorization);
						if (ossConfig.securityToken) {
							xhr.setRequestHeader('x-oss-security-token', ossConfig.securityToken);
						}
						xhr.send(arrayBuffer);
					}).catch(reject);
				});
			},
			
			// 上传大文件（分片上传）
			async uploadLargeFile(fileInfo, ossConfig, ossKey, fileHash) {
				const fileSize = fileInfo.size;
				const chunkSize = this.CHUNK_SIZE;
				const totalChunks = Math.ceil(fileSize / chunkSize);
				
				// 初始化分片上传
				const uploadId = await this.initMultipartUpload(ossConfig, ossKey);
				if (!uploadId) {
					throw new Error('初始化分片上传失败');
				}
				
				// 上传所有分片
				const etags = [];
				for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
					const start = chunkIndex * chunkSize;
					const end = Math.min(start + chunkSize, fileSize);
					
					const etag = await this.uploadChunkWithRetry(
						fileInfo, ossConfig, ossKey, uploadId, chunkIndex, start, end - start
					);
					
					etags.push({
						partNumber: chunkIndex + 1,
						etag: etag
					});
					
					// 更新进度
					const progress = Math.round(((chunkIndex + 1) / totalChunks) * 100);
					this.updateUploadProgress(progress);
				}
				
				// 完成分片上传
				const fileUrl = await this.completeMultipartUpload(ossConfig, ossKey, uploadId, etags);
				
				// 保存文件信息到数据库
				const fileRecord = await this.saveFileRecord({
					name: fileInfo.name,
					path: ossKey,
					url: fileUrl,
					size: fileInfo.size,
					type: fileInfo.type,
					fileHash: fileHash
				});
				
				return {
					url: fileUrl,
					path: ossKey,
					...fileRecord
				};
			},
			
			// 初始化分片上传
			async initMultipartUpload(ossConfig, ossKey) {
				try {
					const xhr = new XMLHttpRequest();
					return new Promise((resolve, reject) => {
						xhr.onload = () => {
							if (xhr.status === 200) {
								const parser = new DOMParser();
								const xmlDoc = parser.parseFromString(xhr.responseText, "text/xml");
								const uploadId = xmlDoc.getElementsByTagName("UploadId")[0].textContent;
								resolve(uploadId);
							} else {
								reject(new Error('初始化分片上传失败'));
							}
						};
						xhr.onerror = () => reject(new Error('初始化分片上传失败'));
						
						xhr.open('POST', `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}?uploads`, true);
						xhr.setRequestHeader('Authorization', ossConfig.authorization);
						if (ossConfig.securityToken) {
							xhr.setRequestHeader('x-oss-security-token', ossConfig.securityToken);
						}
						xhr.send();
					});
				} catch (error) {
					console.error('初始化分片上传失败:', error);
					return null;
				}
			},
			
			// 上传分片（带重试）
			async uploadChunkWithRetry(fileInfo, ossConfig, ossKey, uploadId, chunkIndex, start, length, retryCount = 0) {
				try {
					return await this.uploadChunk(fileInfo, ossConfig, ossKey, uploadId, chunkIndex, start, length);
				} catch (error) {
					if (retryCount < this.MAX_RETRIES) {
						console.warn(`分片 ${chunkIndex} 上传失败，第 ${retryCount + 1} 次重试`);
						await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
						return await this.uploadChunkWithRetry(fileInfo, ossConfig, ossKey, uploadId, chunkIndex, start, length, retryCount + 1);
					} else {
						throw new Error(`分片 ${chunkIndex} 上传失败，已达最大重试次数`);
					}
				}
			},
			
			// 上传单个分片
			async uploadChunk(fileInfo, ossConfig, ossKey, uploadId, chunkIndex, start, length) {
				return new Promise((resolve, reject) => {
					const xhr = new XMLHttpRequest();
					
					xhr.onload = () => {
						if (xhr.status === 200) {
							const etag = xhr.getResponseHeader('ETag');
							resolve(etag);
						} else {
							reject(new Error(`分片上传失败: ${xhr.status}`));
						}
					};
					
					xhr.onerror = () => reject(new Error('分片上传失败'));
					
					// 读取分片数据
					this.readFileChunk(fileInfo, start, length).then(chunkData => {
						const partNumber = chunkIndex + 1;
						xhr.open('PUT', `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}?partNumber=${partNumber}&uploadId=${uploadId}`, true);
						xhr.setRequestHeader('Authorization', ossConfig.authorization);
						if (ossConfig.securityToken) {
							xhr.setRequestHeader('x-oss-security-token', ossConfig.securityToken);
						}
						xhr.send(chunkData);
					}).catch(reject);
				});
			},
			
			// 完成分片上传
			async completeMultipartUpload(ossConfig, ossKey, uploadId, etags) {
				return new Promise((resolve, reject) => {
					const xhr = new XMLHttpRequest();
					
					xhr.onload = () => {
						if (xhr.status === 200) {
							const fileUrl = `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}`;
							resolve(fileUrl);
						} else {
							reject(new Error('完成分片上传失败'));
						}
					};
					
					xhr.onerror = () => reject(new Error('完成分片上传失败'));
					
					// 构建完成请求的XML
					let xml = '<CompleteMultipartUpload>';
					etags.forEach(etag => {
						xml += `<Part><PartNumber>${etag.partNumber}</PartNumber><ETag>${etag.etag}</ETag></Part>`;
					});
					xml += '</CompleteMultipartUpload>';
					
					xhr.open('POST', `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com/${ossKey}?uploadId=${uploadId}`, true);
					xhr.setRequestHeader('Authorization', ossConfig.authorization);
					xhr.setRequestHeader('Content-Type', 'application/xml');
					if (ossConfig.securityToken) {
						xhr.setRequestHeader('x-oss-security-token', ossConfig.securityToken);
					}
					xhr.send(xml);
				});
			},
			
			// 保存文件记录到数据库
			async saveFileRecord(fileData) {
				try {
					const res = await postName('/files/save', {
						...fileData,
						parent: this.parentId
					}, {
						custom: {
							auth: true,
							toast: true,
						},
					});
					
					if (res.code === 200) {
						return res.data;
					} else {
						throw new Error(res.msg || '保存文件记录失败');
					}
				} catch (error) {
					console.error('保存文件记录失败:', error);
					// 即使保存记录失败，也返回基本的文件信息
					return fileData;
				}
			},
			
			// 读取文件为ArrayBuffer
			readFileAsArrayBuffer(fileInfo) {
				return new Promise((resolve, reject) => {
					// #ifdef H5
					fetch(fileInfo.url)
						.then(response => response.blob())
						.then(blob => {
							const reader = new FileReader();
							reader.onload = () => resolve(reader.result);
							reader.onerror = reject;
							reader.readAsArrayBuffer(blob);
						})
						.catch(reject);
					// #endif
					// #ifndef H5
					uni.readFile({
						filePath: fileInfo.url,
						success: (res) => resolve(res.data),
						fail: reject
					});
					// #endif
				});
			},
			
			// 读取文件分片
			readFileChunk(fileInfo, start, length) {
				return new Promise((resolve, reject) => {
					// #ifdef H5
					fetch(fileInfo.url)
						.then(response => response.blob())
						.then(blob => {
							const chunkBlob = blob.slice(start, start + length);
							const reader = new FileReader();
							reader.onload = () => resolve(reader.result);
							reader.onerror = reject;
							reader.readAsArrayBuffer(chunkBlob);
						})
						.catch(reject);
					// #endif
					// #ifndef H5
					uni.readFile({
						filePath: fileInfo.url,
						position: start,
						length: length,
						success: (res) => resolve(res.data),
						fail: reject
					});
					// #endif
				});
			},
			
			// 修复图片信息获取
			imageInfoPromise(url) {
				return new Promise((resolve, reject) => {
					const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif',
						'image/webp',
						'image/svg+xml', 'image/bmp'
					];
					const fileExtension = url.split('.').pop().toLowerCase();
					const isLikelyImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(
						fileExtension);
					if (!isLikelyImage) {
						resolve(null);
						return;
					}
					uni.getImageInfo({
						src: url,
						success: function(images) {
							resolve(images)
						},
						fail: function(err) {
							console.warn('获取图片信息失败，可能不是图片文件:', err);
							resolve(null);
						},
					});
				})
			},

			// 更新上传进度
			updateUploadProgress(progress) {
				this.$emit('upload-progress', progress);
			},
		}
	}
</script>

<style scoped lang="scss">
	.uploadFile {
		margin: 0px;
		display: flex;
		justify-content: center;
		align-items: center;
		width: 100%;

		.uv_upload {
			width: 100%;
			border: 1px;
			border-color: #ffaaff;
			border-style: solid;
			overflow: hidden;

			::v-deep .uv-upload__wrap__preview {
				margin: 0px;
			}

			::v-deep .uv-upload__button {
				margin: 0px;
			}

			::v-deep .uv-upload__deletable {
				height: 25px;
				width: 25px;
				cursor: pointer;
			}

			::v-deep .uv-upload__deletable__icon {
				top: 5px;
				right: 5px
			}

			::v-deep .uv-upload__success {
				bottom: 0px;
				right: 2px;
			}

			::v-deep .uv-upload__deletable {
				z-index: 0;
			}
		}
	}

	.uploadFile_max {
		display: flex;
		justify-content: center;
		align-items: center;

		.uv_upload {
			border: 0px;
			margin-bottom: 5px;

			::v-deep .uv-upload__wrap {
				margin-right: 10px;
				display: flex;
				flex-direction: row;
				flex-wrap: wrap;
				flex: 1;
			}

			::v-deep .uv-upload__wrap__preview {
				margin-right: 10px;
				border-radius: 6px;
				margin-bottom: 6px;
			}
		}
	}
</style>